[toc]

暴力破解分析

分析思路

  1. 找到注册的窗口
  2. 测试注册窗口的反应
  3. 根据反应做出下一步分析的打算
    • 猜测 API,API 下断动态调试
    • 挑出敏感字符串,在程序中搜索
  4. 动态分析,定位关键跳,修改代码
  5. 动态分析,定位关键 call,修改代码

尝试注册

安装 010 Editor v8.0.1 版本,完成后会提示评估版本试用期是30天,需要我们注册

image-20191225135210971

找到注册窗口并试着注册,发现会有错误提示,名称或密码无效

image-20191225135500199

分析注册窗口

  1. 第一种方法是查找弹出窗口的 API

    先在 OD 中附加程序,找到主模块

    image-20191225143336391

    查找当前模块中的名称 Ctrl+N,发现有很多导入函数,使用的是 Qt5 的动态库,由于不清楚 Qt 库函数,故无法定位用的是什么 Qt 窗口函数

    image-20191225141143383

    实际上不论什么界面库,能弹出窗口最终调用的都是 API 函数,可以查找和窗口有关的动态库模块 user32.dll

    image-20191225142434842

    再查找当前模块中的名称,其中和创建窗口有关的 API 有 CreateWindowMessageBoxDialogBoxCreateDialog

    image-20191225143007133

    若不能确定调用的是哪个 API 就在所有的 API 下断,Qt 的窗口函数底层应该调用 CreateWindow,尝试后发现是 CreateWindowExW 这个 API,下断,运行

    image-20191225143745037

    点击 K,栈回溯分析

    image-20191225144532329

    发现有一个 show 函数,调用来自主模块,我们主要分析的也是主模块,依次双击进入,查看附近代码有无可疑或敏感的字符串,若没有就点击下一个,直到找到提示无效的密码

    image-20191225150700646

    发现这儿有一个跳转,查看下面的信息框,根据跳转指示可以知道是哪儿跳转来的,然后一步一步往上跟,进行动态分析

  1. 第二种方法是搜索字符串

    首先注册,弹出名称或密码无效的提示,Ctrl+C 赋值字符串到记事本,在 OD 中右击中文搜索引擎,搜索 ASCII,再右击查找,输入部分字符串,确定后就能找到,再查找下一个,看看有没有多个相同的字符串

    image-20191225192927691

动态分析

找到正确的字符串,再往上跟,分析各个跳转指令的条件

找到关键跳,不能跳过正确的字符串

image-20191225163401915

再往上,找到该跳转来源,即关键函数,将返回值改为 eax = 0x2D 就爆破成功

image-20191225164536708

但先要去掉随机地址,用 010 Editor 打开,修改随机地址 4081 为 0081,再重新加载 OD

进入关键函数,修改

image-20191225172352627

然后保存,打开新保存的程序,授权成功

image-20191225172526657

算法分析

分析思路

  1. 单步跟踪,找到访问用户名密码的代码
  2. 一步一步分析,加注释
  3. 反复推敲,找规律
  4. 写代码验证

算法验证

  1. 进入关键函数,遇到第一个功能 CALL,经调试可发现功能是密码字符串转16进制字节数据,它之上的两个函数分别是判断输入的用户名和密码是否为空,若有一个为空就退出

    image-20191227084950718

  1. 继续往下跟踪分析,可以看到将密码的16进制数据进行一系列运算判断

    K[3] 的值可以有3个,0x9C / 0xFC /0xAC,分别对应不同的算法

    image-20191227090842432

    image-20191227090907361

    image-20191227090923725

    以下是值为 0x9C 的算法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    013BDC5D   .  8A5D DF   MOV BL,BYTE PTR SS:[EBP-0x21]     ;  BL = K[3]
    013BDC60 . 8A7D E1 MOV BH,BYTE PTR SS:[EBP-0x1F] ; BH = K[5]
    013BDC63 . 80FB 9C CMP BL,0x9C ; Switch (cases 9C..FC)
    013BDC66 . 75 70 JNZ SHORT 010Edito.013BDCD8 ; K[3] 应为 0x9C 0xFC 0xAC
    013BDC68 . 8A45 DC MOV AL,BYTE PTR SS:[EBP-0x24] ; AL = K[0]; Case 9C of switch 013BDC63
    013BDC6B . 3245 E2 XOR AL,BYTE PTR SS:[EBP-0x1E] ; AL = K[0] ^ K[6]
    013BDC6E . 8845 E8 MOV BYTE PTR SS:[EBP-0x18],AL ; [ebp - 0x18] = AL
    013BDC71 . 8A45 DD MOV AL,BYTE PTR SS:[EBP-0x23] ; AL = K[1]
    013BDC74 . 3245 E3 XOR AL,BYTE PTR SS:[EBP-0x1D] ; AL = K[1] ^ K[7]
    013BDC77 . FF75 E8 PUSH DWORD PTR SS:[EBP-0x18] ; K[0] ^ K[6]
    013BDC7A . 0FB6C8 MOVZX ECX,AL ; ecx = K[1] ^ K[7] & 0xFF 0扩展填充
    013BDC7D . B8 000100>MOV EAX,0x100 ; eax = 0x100
    013BDC82 . 66:0FAFC8 IMUL CX,AX ; cx = ( K[1] ^ K[7] & 0xFF ) * 0x100
    013BDC86 . 8A45 DE MOV AL,BYTE PTR SS:[EBP-0x22] ; AL = K[2]
    013BDC89 . 32C7 XOR AL,BH ; AL = K[2] ^ K[5]
    013BDC8B . 0FB6C0 MOVZX EAX,AL ; eax = K[2] ^ K[5] & 0xFF
    013BDC8E . 66:03C8 ADD CX,AX ; cx = ( K[1] ^ K[7] & 0xFF ) * 0x100 + K[2] ^ K[5] & 0xFF
    013BDC91 . 0FB7F1 MOVZX ESI,CX ; esi=( (K[1]^K[7]&0xFF)*0x100+K[2]^K[5]&0xFF )&0xFFFF
    013BDC94 . E8 AB9904>CALL 010Edito.00407644 ; AL = ( K[0]^K[6]^0x18 + 0x3D ) ^ 0xA7
    013BDC99 . 0FB6C0 MOVZX EAX,AL
    013BDC9C . 56 PUSH ESI ; 010Edito.02E64618
    013BDC9D . 8947 1C MOV DWORD PTR DS:[EDI+0x1C],EAX ; [edi+0x1c] = eax
    013BDCA0 . E8 23A704>CALL 010Edito.004083C8 ; 计算后判断余数是否为0,若是则返回商,否则返回0
    013BDCA5 . 8B4F 1C MOV ECX,DWORD PTR DS:[EDI+0x1C] ; ecx = [edi+0x1c] = ( K[0]^K[6]^0x18 + 0x3D ) ^ 0xA7
    013BDCA8 . 83C4 08 ADD ESP,0x8
    013BDCAB . 0FB7C0 MOVZX EAX,AX ; eax = (((esi^0x7892)+0x4D30)^0x3421)&0xFFFF / 0xB 或 0
    013BDCAE . 8947 20 MOV DWORD PTR DS:[EDI+0x20],EAX
    013BDCB1 . 85C9 TEST ECX,ECX
    013BDCB3 . 0F84 BC01>JE 010Edito.013BDE75 ; 若 ecx=0,则 eax=0xE7 退出
    013BDCB9 . 85C0 TEST EAX,EAX
    013BDCBB . 0F84 B401>JE 010Edito.013BDE75 ; 若 eax=0,则 eax=0xE7 退出,故上个函数的返回值不能为0
    013BDCC1 . 3D E80300>CMP EAX,0x3E8
    013BDCC6 . 0F87 A901>JA 010Edito.013BDE75 ; eax <= 0x3E8 否则 eax=0xE7 退出
    013BDCCC . 83F9 02 CMP ECX,0x2 ; 判断 ecx
    013BDCCF . 1BF6 SBB ESI,ESI ; 010Edito.02E64618
    013BDCD1 . 23F1 AND ESI,ECX ; esi = 0
    013BDCD3 . E9 B30000>JMP 010Edito.013BDD8B

    以下是值为 0xFC 的算法:

    1
    2
    3
    4
    5
    6
    7
    013BDCD8   > \80FB FC       CMP BL,0xFC
    013BDCDB . 75 1F JNZ SHORT 010Edito.013BDCFC
    013BDCDD . C747 1C FF000>MOV DWORD PTR DS:[EDI+0x1C],0xFF ; Case FC of switch 013BDC63
    013BDCE4 . BE FF000000 MOV ESI,0xFF ; esi = 0xFF
    013BDCE9 . C747 20 01000>MOV DWORD PTR DS:[EDI+0x20],0x1 ; [edi+0x20] = 1
    013BDCF0 . C747 30 01000>MOV DWORD PTR DS:[EDI+0x30],0x1 ; [edi+0x30] = 1
    013BDCF7 . E9 8F000000 JMP 010Edito.013BDD8B

    以下是值为 0xAC 的算法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    013BDCFC   > \80FB AC       CMP BL,0xAC
    013BDCFF . 0F85 70010000 JNZ 010Edito.013BDE75
    013BDD05 . 8A45 DD MOV AL,BYTE PTR SS:[EBP-0x23] ; AL = K[1]; Case AC of switch 013BDC63
    013BDD08 . 3245 E3 XOR AL,BYTE PTR SS:[EBP-0x1D] ; AL = K[1] ^ K[7]
    013BDD0B . 0FB6C8 MOVZX ECX,AL ; ecx = AL & 0xFFFF
    013BDD0E . B8 00010000 MOV EAX,0x100 ; eax = 0x100
    013BDD13 . 66:0FAFC8 IMUL CX,AX ; cx = cx * ax
    013BDD17 . 8A45 DE MOV AL,BYTE PTR SS:[EBP-0x22] ; AL = K[2]
    013BDD1A . 32C7 XOR AL,BH ; AL = AL ^ BH
    013BDD1C . C747 1C 02000>MOV DWORD PTR DS:[EDI+0x1C],0x2 ; [edi+0x1C] = 2
    013BDD23 . 0FB6C0 MOVZX EAX,AL ; eax = AL & 0xFFFF
    013BDD26 . 66:03C8 ADD CX,AX ; cx = cx + ax
    013BDD29 . 0FB7C1 MOVZX EAX,CX ; eax = cx & 0xFFFF
    013BDD2C . 50 PUSH EAX
    013BDD2D . E8 96A604FF CALL 010Edito.004083C8 ; 同 K[3]=0x9C 中的判断余数
    013BDD32 . 0FB7C0 MOVZX EAX,AX ; eax = (((eax^0x7892)+0x4D30)^0x3421)&0xFFFF / 0xB 或 0
    013BDD35 . 83C4 04 ADD ESP,0x4
    013BDD38 . 8947 20 MOV DWORD PTR DS:[EDI+0x20],EAX ; [edi+0x20] = eax
    013BDD3B . 85C0 TEST EAX,EAX ; eax != 0 故上个函数计算后的余数应为0
    013BDD3D . 0F84 32010000 JE 010Edito.013BDE75
    013BDD43 . 3D E8030000 CMP EAX,0x3E8 ; 且 eax <= 0x3E8
    013BDD48 . 0F87 27010000 JA 010Edito.013BDE75
    013BDD4E . 0FB655 E5 MOVZX EDX,BYTE PTR SS:[EBP-0x1B] ; edx = K[9] & 0xFFFF
    013BDD52 . 0FB64D E0 MOVZX ECX,BYTE PTR SS:[EBP-0x20] ; ecx = K[4] & 0xFFFF
    013BDD56 . 0FB6C7 MOVZX EAX,BH ; eax = K[5] & 0xFFFF
    013BDD59 . 33D0 XOR EDX,EAX ; edx = edx ^ eax
    013BDD5B . 0FB645 E4 MOVZX EAX,BYTE PTR SS:[EBP-0x1C] ; eax = K[8] & 0xFFFF
    013BDD5F . 33C8 XOR ECX,EAX ; ecx = ecx ^ eax
    013BDD61 . C1E2 08 SHL EDX,0x8 ; edx << 8
    013BDD64 . 0FB645 E2 MOVZX EAX,BYTE PTR SS:[EBP-0x1E] ; eax = K[6] & 0xFFFF
    013BDD68 . 03D1 ADD EDX,ECX ; edx += ecx
    013BDD6A . 0FB64D DC MOVZX ECX,BYTE PTR SS:[EBP-0x24] ; ecx = K[0] & 0xFFFF
    013BDD6E . C1E2 08 SHL EDX,0x8 ; edx << 8
    013BDD71 . 33C8 XOR ECX,EAX ; ecx = ecx ^ eax
    013BDD73 . 03D1 ADD EDX,ECX ; edx += ecx
    013BDD75 . 68 278C5B00 PUSH 010Edito.005B8C27 ; CCCCCCCC......
    013BDD7A . 52 PUSH EDX
    013BDD7B . E8 0BCA04FF CALL 010Edito.0040A78B ; 同 K[3]=FC 的那个函数
    013BDD80 . 83C4 08 ADD ESP,0x8
    013BDD83 . 8945 F0 MOV DWORD PTR SS:[EBP-0x10],EAX
    013BDD86 . 8947 34 MOV DWORD PTR DS:[EDI+0x34],EAX
    013BDD89 . 8BF0 MOV ESI,EAX ; [ebp-0x10] = [edi+0x34] = esi = eax
  1. 若以上算法成立,都会跳转到 0x13BDD8B,继续进行运算

    对于 K[3] = 0x9C 来说

    image-20191227094148222

    先是将用户名转换为 ASCII 码,然后进行加密计算,加密函数有4个参数,第四个参数 DS:[EDI+0x20] 来自之前的运算结果

    image-20191227094651580

    第三第二个参数默认为0和1,第一个参数是用户名字符串

    加密之后分别比较 K[4] / K[5] / K[6] / K[7] 与加密后再计算的值,最后比较 [EDI+0x1C] >= 9,若成立就正确

    image-20191227100649518

    其中 [EDI+0x1C] 也来自之前运算的结果

    image-20191227103649907

[edi+0x1C] >= 9 成立,就跳转,之后往下走成功

image-20191227165840569

对于 K[3] = 0xFC 来说

image-20191227155851506

传入的4个参数与上面不同,第四个参数 DS:[EDI+0x20] 恒为1

image-20191227143126932

第三个参数恒为 0xFF,第二个参数恒为0,第一个参数是用户名字符串

加密之后分别比较 K[4] / K[5] / K[6] / K[7] 与加密后再计算的值,与 0x9C 不同的是,比较完 K[7] 后,由于 BL = 0xFC,故需要跳转到下面进行运算

image-20191227161224374

这个函数传入2个参数,一个是之前加密函数的运算结果,另一个是 K[2]K[1]K[0] 组成的字符,进入函数内部

image-20191227162001694

1
2
3
4
5
6
7
8
9
eax = 0xF0F0F0F1
ecx = ((ecx ^ edx ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF
结果的低位 eax = eax * ecx & 0xFFFF
结果的高位 edx = eax * ecx & 0xFFFF0000 >> 32
edx = edx >> 4
eax = (edx << 4) + edx
ecx = ecx - eax
eax = 0
eax = edx (ZF = 1时才能移动)

image-20191227163947265

若想实现 eax = edx,需 ecx == eax,即

((ecx ^ edx ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF

==

(0xF0F0F0F1 * (((ecx ^ edx ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF) & 0xFFFF0000 >> 32) >> 4

+ (0xF0F0F0F1 * (((ecx ^ edx ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF) & 0xFFFF0000 >> 32)

其中,参数1是 ecx,参数2是 edx

那么 0x13BDE1F 不会跳转,接着往下走就会成功

对于 K[3] = 0xAC 来说

首先计算

eax = (((K[1] ^ k[7] & 0xFF) * 0x100) & 0xFF + (k[2] ^ k[5] & 0xFF)) & 0xFFFF

eax = (((eax ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF

要求 eax % 0xB == 0 && eax / 0xB <= 0x3E8 成立

然后计算

edx = (((K[9] ^ K[5] << 8) + (K[4] ^ K[8])) << 8) + (K[0] ^ K[6]) 其中 K[x] = K[x] & 0xFFFF

进入函数 0x0040A78B,第一个参数为 edx,第二个参数为 0x5B8C27

((ecx ^ 0x5B8C27 ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF

==

(0xF0F0F0F1 * (((ecx ^ 0x5B8C27 ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF) & 0xFFFF0000 >> 32) >> 4

+ (0xF0F0F0F1 * (((ecx ^ 0x5B8C27 ^ 0x22C078) - 0x2C175) ^ 0xFFE53167 & 0xFFFFFF) & 0xFFFF0000 >> 32)

其中 ecx = 参数1

image-20191228100134738

先是将用户名转换为 ASCII 码,然后进行加密计算,加密函数有4个参数,第四个参数 DS:[EDI+0x20] 来自之前的运算结果

image-20191228100251496

第三第二个参数默认为0和1,第一个参数是用户名字符串

加密之后分别比较 K[4] / K[5] / K[6] / K[7] 与加密后再计算的值,与 0x9C 不同的是,比较完 K[7] 后,由于 BL = 0xAC,故需要跳过比较 0x9C 和 0xFC

image-20191228100621144

跳到这儿

image-20191228101441147

[ebp-0x10] 来自此函数,故需要函数的计算结果不为0,与 K[3] = 0xFC 中那个相同函数 0x0040A78B 一样

image-20191228100834068

0x4389 来自关键函数的第二个参数

image-20191228102022515

注册机编写

以 K[3] = 0x9C 为例

关键一点是对用户名字符串的加密函数,想要简单地编写函数,可以使用 IDA Pro,找到该函数入口地址 0x13BD120,按 F5 得到 C 代码,稍微修改一下就可用了

image-20191227140442629

函数中用到的数组可以记下它的地址 0x2E64148

image-20191227141232609

转到 OD 中进行拷贝,当然也可选择 Word 类型

image-20191227141607434

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// decode010.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <windows.h>
#include <time.h>
DWORD g_EcodeArray[] = {
0x39cb44b8, 0x23754f67, 0x5f017211, 0x3ebb24da, 0x351707c6, 0x63f9774b, 0x17827288, 0x0fe74821, 0x5b5f670f, 0x48315ae8, 0x785b7769, 0x2b7a1547, 0x38d11292, 0x42a11b32, 0x35332244, 0x77437b60,
0x1eab3b10, 0x53810000, 0x1d0212ae, 0x6f0377a8, 0x43c03092, 0x2d3c0a8e, 0x62950cbf, 0x30f06ffa, 0x34f710e0, 0x28f417fb, 0x350d2f95, 0x5a361d5a, 0x15cc060b, 0x0afd13cc, 0x28603bcf, 0x3371066b,
0x30cd14e4, 0x175d3a67, 0x6dd66a13, 0x2d3409f9, 0x581e7b82, 0x76526b99, 0x5c8d5188, 0x2c857971, 0x15f51fc0, 0x68cc0d11, 0x49f55e5c, 0x275e4364, 0x2d1e0dbc, 0x4cee7ce3, 0x32555840, 0x112e2e08,
0x6978065a, 0x72921406, 0x314578e7, 0x175621b7, 0x40771dbf, 0x3fc238d6, 0x4a31128a, 0x2dad036e, 0x41a069d6, 0x25400192, 0x00dd4667, 0x6afc1f4f, 0x571040ce, 0x62fe66df, 0x41db4b3e, 0x3582231f,
0x55f6079a, 0x1ca70644, 0x1b1643d2, 0x3f7228c9, 0x5f141070, 0x3e1474ab, 0x444b256e, 0x537050d9, 0x0f42094b, 0x2fd820e6, 0x778b2e5e, 0x71176d02, 0x7fea7a69, 0x5bb54628, 0x19ba6c71, 0x39763a99,
0x178d54cd, 0x01246e88, 0x3313537e, 0x2b8e2d17, 0x2a3d10be, 0x59d10582, 0x37a163db, 0x30d6489a, 0x6a215c46, 0x0e1c7a76, 0x1fc760e7, 0x79b80c65, 0x27f459b4, 0x799a7326, 0x50ba1782, 0x2a116d5c,
0x63866e1b, 0x3f920e3c, 0x55023490, 0x55b56089, 0x2c391fd1, 0x2f8035c2, 0x64fd2b7a, 0x4ce8759a, 0x518504f0, 0x799501a8, 0x3f5b2cad, 0x38e60160, 0x637641d8, 0x33352a42, 0x51a22c19, 0x085c5851,
0x032917ab, 0x2b770ac7, 0x30ac77b3, 0x2bec1907, 0x035202d0, 0x0fa933d3, 0x61255df3, 0x22ad06bf, 0x58b86971, 0x5fca0de5, 0x700d6456, 0x56a973db, 0x5ab759fd, 0x330e0be2, 0x5b3c0ddd, 0x495d3c60,
0x53bd59a6, 0x4c5e6d91, 0x49d9318d, 0x103d5079, 0x61ce42e3, 0x7ed5121d, 0x14e160ed, 0x212d4ef2, 0x270133f0, 0x62435a96, 0x1fa75e8b, 0x6f092fbe, 0x4a000d49, 0x57ae1c70, 0x004e2477, 0x561e7e72,
0x468c0033, 0x5dcc2402, 0x78507ac6, 0x58af24c7, 0x0df62d34, 0x358a4708, 0x3cfb1e11, 0x2b71451c, 0x77a75295, 0x56890721, 0x0fef75f3, 0x120f24f1, 0x01990ae7, 0x339c4452, 0x27a15b8e, 0x0ba7276d,
0x60dc1b7b, 0x4f4b7f82, 0x67db7007, 0x4f4a57d9, 0x621252e8, 0x20532cfc, 0x6a390306, 0x18800423, 0x19f3778a, 0x462316f0, 0x56ae0937, 0x43c2675c, 0x65ca45fd, 0x0d604ff2, 0x0bfd22cb, 0x3afe643b,
0x3bf67fa6, 0x44623579, 0x184031f8, 0x32174f97, 0x4c6a092a, 0x5fb50261, 0x01650174, 0x33634af1, 0x712d18f4, 0x6e997169, 0x5dab7afe, 0x7c2b2ee8, 0x6edb75b4, 0x5f836fb6, 0x3c2a6dd6, 0x292d05c2,
0x052244db, 0x149a5f4f, 0x5d486540, 0x331d15ea, 0x4f456920, 0x483a699f, 0x3b450f05, 0x3b207c6c, 0x749d70fe, 0x417461f6, 0x62b031f1, 0x2750577b, 0x29131533, 0x588c3808, 0x1aef3456, 0x0f3c00ec,
0x7da74742, 0x4b797a6c, 0x5ebb3287, 0x786558b8, 0x00ed4ff2, 0x6269691e, 0x24a2255f, 0x62c11f7e, 0x2f8a7dcd, 0x643b17fe, 0x778318b8, 0x253b60fe, 0x34bb63a3, 0x5b03214f, 0x5f1571f4, 0x1a316e9f,
0x7acf2704, 0x28896838, 0x18614677, 0x1bf569eb, 0x0ba85ec9, 0x6aca6b46, 0x1e43422a, 0x514d5f0e, 0x413e018c, 0x307626e9, 0x01ed1dfa, 0x49f46f5a, 0x461b642b, 0x7d7007f2, 0x13652657, 0x6b160bc5,
0x65e04849, 0x1f526e1c, 0x5a0251b6, 0x2bd73f69, 0x2dbf7acd, 0x51e63e80, 0x5cf2670f, 0x21cd0a03, 0x5cff0261, 0x33ae061e, 0x3bb6345f, 0x5d814a75, 0x257b5df4, 0x0a5c2c5b, 0x16a45527, 0x16f23945
};
int __cdecl EncodeUsername(const char *pszUserName, int a2, char a3, unsigned __int16 a4)
{
const char *v4; // edx@1
signed int v5; // esi@1
signed int v6; // edi@1
unsigned __int8 v7; // bl@2
int v8; // eax@3
int v9; // ecx@3
int v10; // ecx@4
int result; // eax@4
int v12; // ecx@5
unsigned __int8 v13; // [sp+8h] [bp-10h]@2
unsigned __int8 v14; // [sp+Ch] [bp-Ch]@2
unsigned __int8 v15; // [sp+10h] [bp-8h]@2
int v16; // [sp+14h] [bp-4h]@1
v4 = pszUserName;
v16 = 0;
v5 = strlen(pszUserName);
v6 = 0;
if (v5 <= 0)
{
result = 0;
}
else
{
v13 = 0;
v14 = 0;
v7 = 15 * a4;
v15 = 17 * a3;
do
{
v8 = toupper(v4[v6]);
v9 = v16 + g_EcodeArray[v8];
if (a2)
{
v10 = g_EcodeArray[v7]
+ g_EcodeArray[v15]
+ g_EcodeArray[(unsigned __int8)(v8 + 47)] * (g_EcodeArray[(unsigned __int8)(v8 + 13)] ^ v9);
result = g_EcodeArray[v14] + v10;
v16 = g_EcodeArray[v14] + v10;
}
else
{
v12 = g_EcodeArray[v7]
+ g_EcodeArray[v15]
+ g_EcodeArray[(unsigned __int8)(v8 + 23)] * (g_EcodeArray[(unsigned __int8)(v8 + 63)] ^ v9);
result = g_EcodeArray[v13] + v12;
v16 = g_EcodeArray[v13] + v12;
}
v14 += 19;
++v6;
v15 += 9;
v7 += 13;
v13 += 7;
v4 = pszUserName;
} while (v6 < v5);
}
return result;
}
int main()
{
srand(time(NULL));
//初始化随机密码
int dwRet = rand() % 0x3E8;
byte K[10] = { 0x11, 0x22, 0x33, 0x9C, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00 };
//输入用户名
char szName[100] = { 0 };
printf("please input name:", szName);
scanf_s("%s", szName, 100);
//将返回值赋给密码数组
DWORD dwKey = EncodeUsername(szName, 1, 0, dwRet);
//cmp K[4], eax & 0xFF == 0
//cmp K[5], eax >>8 & 0xFF == 0
//cmp K[6], eax >> 16 & 0xFF == 0
//cmp K[7], eax >>24 & 0xFF == 0
K[4] = dwKey & 0xFF;
K[5] = dwKey >> 8 & 0xFF;
K[6] = dwKey >> 16 & 0xFF;
K[7] = dwKey >> 24 & 0xFF;
//穷举剩余的字节
//AL = ( K[0]^K[6]^0x18 + 0x3D ) ^ 0xA7
while (TRUE) {
byte k0 = rand() % 0xFF;
byte k6 = K[6];
byte al = (k0 ^ k6 ^ 0x18 + 0x3D) ^ 0xA7;
if (al >= 9) {
K[0] = k0;
K[6] = k6;
break;
}
}
//esi = ((K[1] ^ K[7] & 0xFF) * 0x100 + K[2] ^ K[5] & 0xFF) & 0xFFFF
//eax = (((esi^0x7892)+0x4D30)^0x3421)&0xFFFF / 0xB
//计算后判断余数是否为0,若是则返回商,否则返回0
//且商 <= 0x3E8
while (TRUE) {
byte k1 = rand() % 0xFF;
byte k2 = rand() % 0xFF;
byte k5 = K[5];
byte k7 = K[7];
DWORD esi = ((k1 ^ k7 & 0xFF) * 0x100 + k2 ^ k5 & 0xFF) & 0xFFFF;
DWORD eax = (((esi ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
if (eax % 0xB == 0 && eax / 0xB == dwRet) {
K[1] = k1;
K[2] = k2;
K[5] = k5;
K[7] = k7;
break;
}
}
//打印输出密码
printf("%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X",
K[0], K[1], K[2], K[3], K[4], K[5], K[6], K[7], K[8], K[9]);
getchar();
return 0;
}

算法总结

针对 K[3] = 0x9C :

  1. 判断用户名密码是否为空

  2. 将密码字符串转为16进制字节数据

  3. 验证密码16进制数据

    • AL = ( K[0] ^ K[6] ^ 0x18 + 0x3D ) ^ 0xA7 != 0

    • esi = ( ( K[1] ^ K[7] & 0xFF ) * 0x100 + K[2] ^ K[5] & 0xFF ) & 0xFFFF

      eax = ((( esi ^ 0x7892 ) + 0x4D30 ) ^ 0x3421 ) & 0xFFFF / 0xB

      且 ( eax % 0xB == 0 && eax / 0xB <= 0x3E8 )

  4. 将用户名转为 ASCII 码

  5. 使用用户名计算出一个 key,进行一系列操作后要和密码 K[4] ~ K[7] 相等

    ​ K[4] = key & 0xFF;
    ​ K[5] = key >> 8 & 0xFF;
    ​ K[6] = key >> 16 & 0xFF;
    ​ K[7] = key >> 24 & 0xFF;

  6. 判断参数 ( K[0]^K[6]^0x18 + 0x3D ) ^ 0xA7 >= 9

注册机总结

  1. 版本一
    • 随机字节,穷举找出符合条件的值
  2. 版本二
    • 指定用户名,调用加密函数求出 key
    • 将 key 拆分,指定到密码字节数组

网络验证分析

输入正确的用户名密码后会有网络验证

image-20191227112813413

在关键函数后的网络验证函数中修改跳转 je 为 jmp

image-20191227190653139

image-20191227190448969

在下一个函数中修改编辑为就能过掉网络验证

image-20191227190710729